package com.github.ecbp.common.security.filter;

import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.github.ecbp.common.constant.CacheKeyConstant;
import com.github.ecbp.common.redis.RedisUtil;
import com.github.ecbp.common.security.config.JwtTokenConfig;
import com.github.ecbp.common.security.utils.CommonAdminUtils;
import com.github.ecbp.common.security.utils.JwtTokenUtil;
import com.github.ecbp.common.security.vo.DefaultSecurityConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;

import java.util.*;

/**
 * 动态权限数据源，用于获取动态权限规则
 */
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private JwtTokenConfig jwtTokenConfig;

    @Value("${spring.application.name}")
    private String projectName;

    public Map<String, DefaultSecurityConfig> loadDataSource() {
        Map<String, DefaultSecurityConfig> configAttributeMap = new HashMap<>();
        String result = redisUtil.get(CacheKeyConstant.SECURITY_KEY, 1);
        if (null != result) {
            Map<String, Object> stringJSONObjectMap = JSON.parseObject(result, Map.class);
            if (!CollectionUtils.isEmpty(stringJSONObjectMap)) {
                for (Map.Entry<String, Object> entry : stringJSONObjectMap.entrySet()) {
                    configAttributeMap.put(entry.getKey(), JSON.parseObject(entry.getValue().toString(), DefaultSecurityConfig.class));
                }
            }
        }
        return configAttributeMap;
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取当前访问的路径
        String path = URLUtil.getPath(((FilterInvocation) o).getRequestUrl());

        if (null == SecurityContextHolder.getContext().getAuthentication()
                || JwtTokenUtil.ANONYMOUS_USER.equals(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) {
            LOGGER.info("[ 用户未登录: {} 禁止访问, TOKEN:{}]", path, CommonAdminUtils.getTokenFromHeader());
            throw new AccessDeniedException("用户未登录, 禁止访问");
        }


        // 需要登录白名单请求直接放行
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String loginUrl : jwtTokenConfig.getLoginUrls()) {
            if (pathMatcher.match(loginUrl, path)) {
                LOGGER.info("[ 只验证登录接口: {} ]", path);
                return Collections.emptyList();
            }
        }

        Map<String, DefaultSecurityConfig> configAttributeMap = this.loadDataSource();
        List<ConfigAttribute> configAttributes = new ArrayList<>();
        //获取访问该路径所需资源
        for (Map.Entry<String, DefaultSecurityConfig> entry : configAttributeMap.entrySet()) {
            if (entry.getKey().startsWith(projectName + CacheKeyConstant.PATH_SEPARATOR)
                    && pathMatcher.match(entry.getKey().replace(projectName + CacheKeyConstant.PATH_SEPARATOR, ""), path)) {
                configAttributes.add(entry.getValue());
            }
        }

        // 获得所有的匹配的路由权限配置, 未设置操作请求权限
        if (CollectionUtils.isEmpty(configAttributes)) {
            LOGGER.info("[ 接口'{}'未配置访问权限]", path);
            configAttributes.add(new DefaultSecurityConfig(JwtTokenUtil.NO_CONFIG_URL));
        }
        return configAttributes;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        List<ConfigAttribute> configAttributes = new ArrayList<>();
        Map<String, DefaultSecurityConfig> configAttributeMap = this.loadDataSource();
        for (Map.Entry<String, DefaultSecurityConfig> entry : configAttributeMap.entrySet()) {
            configAttributes.add(entry.getValue());
        }
        return configAttributes;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

}
